<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>4.8.2-计算属性 computed 与 lazy</title>
</head>

<body>
    <div id="container"></div>
</body>
<script>
    /**
     * 把传递给effect 的函数看作一个 getter，那么这个 getter 函数可以返回任何值
     * - 传递给 effect 函数的参数 fn 才是真正的副作用函数，
     *    而 effectFn 是我们包装后的副作用函数
     * - 将其保存到 res 变量中，然后将其作为 effectFn 函数的返回值
     * */

    // 存储副作用函数的桶
    const bucket = new WeakMap()
    // 原始数据
    const data = { foo: 1, bar: 2 }
    // 对原始数据的代理
    const obj = new Proxy(data, {
        // 拦截读取操作
        get(target, key) {
            // 将副作用函数 activeEffect 添加到存储副作用函数的桶中
            track(target, key)
            // 返回属性值
            return target[key]
        },
        // 拦截设置操作
        set(target, key, newVal) {
            // 设置属性值
            target[key] = newVal
            // 把副作用函数从桶里取出并执行
            trigger(target, key)
        }
    })

    // 在 get 拦截函数内调用 track 函数追踪变化
    function track(target, key) {
        // 没有 activeEffect，直接 return
        if (!activeEffect) return
        let depsMap = bucket.get(target)
        if (!depsMap) {
            bucket.set(target, (depsMap = new Map()))
        }
        let deps = depsMap.get(key)
        if (!deps) {
            depsMap.set(key, (deps = new Set()))
        }
        // 把当前激活的副作用函数添加到依赖集合 deps 中
        deps.add(activeEffect)
        // deps 就是一个与当前副作用函数存在联系的依赖集合
        // 将其添加到 activeEffect.deps 数组中
        activeEffect.deps.push(deps)
    }
    // 在 set 拦截函数内调用 trigger 函数触发变化
    function trigger(target, key) {
        const depsMap = bucket.get(target)
        if (!depsMap) return
        const effects = depsMap.get(key)
        const effectsToRun = new Set()
        effects && effects.forEach(effectFn => {
            // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同，则不触发执行
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
        effectsToRun.forEach(effectFn => {
            // 如果一个副作用函数存在调度器，则调用该调度器，并将副作用函数作为参数传递
            if (effectFn.options.scheduler) {
                effectFn.options.scheduler(effectFn)
            } else {
                // 否则直接执行副作用函数（之前的默认行为）
                effectFn()
            }
        })
    }

    // 用一个全局变量存储被注册的副作用函数
    let activeEffect
    // effect 栈
    // 个人理解，始终都会留着一个是最先执行的
    const effectStack = []

    function effect(fn, options = {}) {
        const effectFn = () => {
            cleanup(effectFn)
            // 当调用 effect 注册副作用函数时，将副作用函数赋值给 activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压入栈中
            effectStack.push(effectFn)
            // 将 fn 的执行结果存储到 res 中
            const res = fn()  // 新增
            // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并把 activeEffect 还原为之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            // 将 res 作为 effectFn 的返回值
            return res; // 新增
        }
        // 将 options 挂载到 effectFn 上
        effectFn.options = options
        // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
        effectFn.deps = []
        // 只有非 lazy 的时候，才执行
        if (!options.lazy) {
            // 执行副作用函数
            effectFn()
        }
        // 将副作用函数作为返回值返回
        return effectFn
    }

    function cleanup(effectFn) {
        // 遍历 effectFn.deps 数组
        for (let i = 0; i < effectFn.deps.length; i++) {
            // deps 是依赖集合
            const deps = effectFn.deps[i]
            // 将 effectFn 从依赖集合中移除
            deps.delete(effectFn)
        }
        // 最后需要重置 effectFn.deps 数组
        effectFn.deps.length = 0
    }

    // 定义一个任务队列
    const jobQueue = new Set()
    // 使用 Promise.resolve() 创建一个 promise 实例，我们用它将一个任务添加到微任务队列
    const p = Promise.resolve()
    // 一个标志代表是否正在刷新队列
    let isFlushing = false
    function flushJob() {
        // 如果队列正在刷新，则什么都不做
        if (isFlushing) return
        // 设置为 true，代表正在刷新
        isFlushing = true
        // 在微任务队列中刷新 jobQueue 队列
        // 微任务会在宏任务之后执行
        p.then(() => {
            jobQueue.forEach(job => job())
        }).finally(() => {
            // 结束后重置 isFlushing
            isFlushing = false
        })
    }

    
    const effectFn = effect(
        // getter 返回 obj.foo 与 obj.bar 的和
        () => obj.foo + obj.bar, 
        // options 
        { 
            lazy: true 
        }
    )
    // value 是 getter 的返回值
    const value = effectFn();
    console.log(value);

</script>

</html>